/*
 * Copyright 2010 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.doctool.custom;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * Used by trunk/doc/build.xml to generate the packages.properties file.
 */
public class FindPackages {

  /**
   * A list of regular expressions to exclude. For readability, a '.' character
   * will be interpreted literally (i.e., it will be transformed into '\.'
   * before being compiled). Add rules here as needed.
   */
  private static final String[] EXCLUSIONS = {
      "^com.example.", "^com.google.gwt.dev(.|$)", "^com.google.gwt.emul.",
      "^com.google.gwt.examples(.|$)", "^com.google.gwt.i18n.server(.|$)",
      "^com.google.gwt.i18n.tools", "^com.google.gwt.lang",
      "^com.google.gwt.junit(.|$)", "^com.google.gwt.resources.css(.|$)",
      "^com.google.gwt.resources.rg(.|$)",
      "^com.google.gwt.rpc.client.ast(.|$)", "^com.google.gwt.soyc(.|$)",
      "^com.google.gwt.validation(.|$)",
      "^com.google.gwt.user.\\w+.rpc.core.",
      "^javax.", "^junit.", "^org.",
      ".impl(.|$)", ".rebind(.|$)"
  };

  /**
   * A list of emulated packages in the java.* namespace, to be emitted as
   * the JAVA_PKGS property.  Add packages here as needed.
   */
  private static final String[] JAVA_PKGS = {
    "java.beans",
    "java.io",
    "java.lang", "java.lang.annotation", "java.lang.reflect",
    "java.math",
    "java.nio.charset",
    "java.security",
    "java.sql",
    "java.text",
    "java.util", "java.util.concurrent", "java.util.concurrent.atomic", "java.util.function", "java.util.logging", "java.util.stream"
  };

  /**
   * User packages to include, regardless of exclusions.  Add packages here
   * as needed.
   */
  private static final String[] PACKAGE_WHITELIST = {
      "com.google.gwt.i18n.rebind.format", "com.google.gwt.i18n.rebind.keygen",
      "com.google.gwt.junit.client"};

  /**
   * Source directories under the root directory to traverse.  Add directories
   * here as needed.
   */
  private static final String[] SOURCE_DIRS = {
      "user/src", "user/javadoc", "user/super", "dev/core/src",
      "dev/core/super"};

  /**
   * Individual user classes to include, even if the rest of their packages
   * is not included.  Add classes here as needed.
   */
  private static final String[] USER_CLASSES = {
      "user/src/com/google/gwt/junit/tools/GWTTestSuite.java",
      "user/src/com/google/gwt/i18n/rebind/LocaleUtils.java",
      "user/src/com/google/gwt/i18n/server/GwtLocaleFactoryImpl.java",
      "user/src/com/google/gwt/i18n/server/GwtLocaleImpl.java"};

  private static Pattern exclusions;
  static {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < EXCLUSIONS.length; i++) {
      String ex = EXCLUSIONS[i];
      ex = ex.replace(".", "\\.");
      if (i < EXCLUSIONS.length - 1) {
        sb.append(ex + "|");
      } else {
        sb.append(ex);
      }
    }
    exclusions = Pattern.compile(sb.toString());
  }

  public static void main(String[] args) {
    if (args.length < 1) {
      System.err.println("usage: java com.google.doctool.custom.FindPackages <root dir>");
      System.exit(1);
    }

    try {
      File rootDir = new File(args[0]);
      // Output to <root>/doc/packages.properties
      File build = new File(rootDir, "build");
      File buildOut = new File(build, "out");
      File outFile = new File(buildOut, "packages.properties");
      PrintStream out = new PrintStream(new FileOutputStream(outFile), false, "UTF-8");

      out.println("# THIS FILE IS AUTOMATICALLY GENERATED BY");
      out.println("# com.google.doctool.custom.FindPackages");
      out.println("#");
      out.println("# This file contains all of the user javadoc packages");
      out.println("#");
      out.println("# JRE emulation packages");
      out.println("JAVA_PKGS=\\");
      for (int i = 0; i < JAVA_PKGS.length; i++) {
        if (i < JAVA_PKGS.length - 1) {
          out.println(JAVA_PKGS[i] + ";\\");
        } else {
          out.println(JAVA_PKGS[i]);
        }
      }
      out.println("# The last package should not have a trailing semicolon");
      out.println("");
      out.println("# Individual classes to include when we don't want to include an entire package");
      out.println("USER_CLASSES=\\");

      // Output a package-info.java once for each package
      Set<String> classPaths = new HashSet<String>();
      for (int i = 0; i < USER_CLASSES.length; i++) {
        String className = USER_CLASSES[i];
        String classPath = className.substring(0, className.lastIndexOf('/'));
        if (!classPaths.contains(classPath)) {
          classPaths.add(classPath);
          out.println("${gwt.root}/" + classPath + "/package-info.java" + ":\\");
        }
        if (i < USER_CLASSES.length - 1) {
          out.println("${gwt.root}/" + className + ":\\");
        } else {
          out.println("${gwt.root}/" + className);
        }
      }
      out.println("");
      out.println("# Packages to include");
      out.println("USER_PKGS=\\");

      Set<String> packages = new HashSet<String>();
      for (String dir : SOURCE_DIRS) {
        File f = new File(rootDir, dir);
        findPackages(f, null, packages);
      }
      for (String s : PACKAGE_WHITELIST) {
        packages.add(s);
      }

      ArrayList<String> packageList = new ArrayList<String>(packages);
      Collections.sort(packageList);

      for (int i = 0; i < packages.size(); i++) {
        if (i < packages.size() - 1) {
          out.println(packageList.get(i) + ";\\");
        } else {
          out.println(packageList.get(i));
        }
      }
      out.println("# The last package should not have a trailing semicolon");
      out.close();
    } catch (IOException e) {
      System.err.println("Got I/O Exception: " + e);
      System.exit(2);
    }
  }

  /**
   * Recursively find java packages under the given directory.
   *
   * @param dir the root directory
   * @param packageName the package name so far
   * @param packages the set of packages to output to
   */
  private static void findPackages(File dir, String packageName,
      Set<String> packages) {
    File[] files = dir.listFiles();
    if (files == null) {
      return;
    }
    boolean hasJava = false;
    for (File f : files) {
      String name = f.getName();
      if (f.isDirectory()) {
        String subPackage = packageName == null ? name : packageName + "."
            + name;
        findPackages(f, subPackage, packages);
      } else {
        // If there is a java file in the directory, include the package
        // for further processing.
        if (!hasJava && name.endsWith(".java")) {
          hasJava = true;
        }
      }
    }

    // Clean up translatable/ and super/ paths
    // Emit the package name if not excluded
    if (hasJava && packageName != null) {
      int index;
      if ((index = packageName.indexOf(".translatable.")) != -1) {
        packageName = packageName.substring(index + 14);
      }
      if ((index = packageName.indexOf(".super.")) != -1) {
        packageName = packageName.substring(index + 7);
      }
      if (!exclusions.matcher(packageName).find()) {
        packages.add(packageName);
      }
    }
  }
}
